using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using WalletWasabi.Extensions;
using WalletWasabi.Helpers;

namespace WalletWasabi.Userfacing;

public static class EndPointParser
{
	public static string ToString(this EndPoint me, int defaultPort)
	{
		string host;
		int port;
		if (me is DnsEndPoint dnsEndPoint)
		{
			host = dnsEndPoint.Host;
			port = dnsEndPoint.Port;
		}
		else if (me is IPEndPoint ipEndPoint)
		{
			host = ipEndPoint.Address.ToString();
			port = ipEndPoint.Port;
		}
		else
		{
			throw new FormatException($"Invalid endpoint: {me}");
		}

		if (port == 0)
		{
			port = defaultPort;
		}

		var endPointString = $"{host}:{port}";

		return endPointString;
	}

	/// <param name="defaultPort">If invalid and it's needed to use, then this function returns false.</param>
	public static bool TryParse(string? endPointString, int defaultPort, [NotNullWhen(true)] out EndPoint? endPoint)
	{
		endPoint = null;

		try
		{
			if (string.IsNullOrWhiteSpace(endPointString))
			{
				return false;
			}

			endPointString = Guard.Correct(endPointString);
			endPointString = endPointString.TrimEnd(':', '/');
			endPointString = endPointString.TrimStart("bitcoin-p2p://", StringComparison.OrdinalIgnoreCase);
			endPointString = endPointString.TrimStart("tcp://", StringComparison.OrdinalIgnoreCase);

			var parts = endPointString.Split(':', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim().TrimEnd('/').TrimEnd()).ToArray();

			if (parts.Length == 0)
			{
				return false;
			}

			ushort p;
			int port;

			if (parts.Length == 1)
			{
				if (IsValidPort(defaultPort.ToString(), out p))
				{
					port = p;
				}
				else
				{
					return false;
				}
			}
			else if (parts.Length == 2)
			{
				var portString = parts[1];

				if (IsValidPort(portString, out p))
				{
					port = p;
				}
				else
				{
					return false;
				}
			}
			else
			{
				return false;
			}

			var host = parts[0];

			if (host == "localhost")
			{
				host = IPAddress.Loopback.ToString();
			}

			if (IPAddress.TryParse(host, out IPAddress? addr))
			{
				endPoint = new IPEndPoint(addr, port);
			}
			else
			{
				endPoint = new DnsEndPoint(host, port);
			}

			return true;
		}
		catch
		{
			return false;
		}
	}

	// Checks a port is a number within the valid port range (0 - 65535).
	private static bool IsValidPort(string port, out ushort p)
	{
		return ushort.TryParse(port, out p);
	}

	public static Result<EndPoint, string> Parse(string endpoint)
	{
		if(endpoint.Split(":", StringSplitOptions.RemoveEmptyEntries) is [var hostPart, var portPart])
		{
			if (ushort.TryParse(portPart, out var port))
			{
				return IPAddress.TryParse(hostPart, out var ipAddress)
					? new IPEndPoint(ipAddress, port)
					: hostPart == "localhost"
						? new IPEndPoint(IPAddress.Loopback, port)
						: new DnsEndPoint(hostPart, port);
			}
		}

		return Result<EndPoint, string>.Fail($"Format error for endpoint '{endpoint}'");
	}
}
